/*
 * Copyright (C) 2001-2005 Pleasant nightmare studio
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.pleasantnightmare.noraxidium.support;

import org.apache.log4j.Logger;
import org.lwjgl.util.Rectangle;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.tiled.TileSet;
import org.newdawn.slick.tiled.TiledMap;
import org.pleasantnightmare.noraxidium.SupportContainer;
import org.pleasantnightmare.noraxidium.collision.CollisionBound;
import org.pleasantnightmare.noraxidium.collision.RectBound;
import org.pleasantnightmare.noraxidium.math.GameVector2f;
import org.pleasantnightmare.noraxidium.model.LevelMap;

import java.awt.Dimension;
import java.util.LinkedList;
import java.util.List;

/**
 * Takes over all responsibility about maps. It should contain code for returning and constructing data
 * that is encoded into map files, and no other code besides this one should ever touch map tiles ID's.
 *
 * @author deus
 * @version 1.0
 * @since Jul 6, 2010 2:38:21 PM
 */
public class MapSupport implements Support {
  private static final Logger LOGGER = Logger.getLogger(MapSupport.class);

  private static final int TILE_COLLISION = 0;
  private static final int TILE_STEALTH = 1;
  private static final int TILE_INTERACTIVE = 2;
  private static final int TILE_START_POSITION = 3;

  private static final String TILESET_DIRECTORY = "maps/tilesets";
  private static final int TILESET_REPRESENTATION = 0;
  private static final int TILESET_GAMEPLAY = 1;

  private static final int LAYERS_COUNT = 3;

  public static final Dimension TILE_SIZE = new Dimension(32, 32);
  public static final int LAYER_REPRESENTATION_BACK = 0;
  public static final int LAYER_REPRESENTATION_FRONT = 1;
  public static final int LAYER_GAMEPLAY_LOGIC = 2;

  private SupportContainer supportContainer;
  private LevelMap currentMap;

  @Override public void setSupportContainer(SupportContainer supportContainer) {
    this.supportContainer = supportContainer;
  }

  public LevelMap loadMap(String mapName) {
    try {
      TiledMap map = new TiledMap(mapName);
      LevelMap level = new LevelMap(mapName, map);

      validateMap(level);
      findStartPosition(level);
      constructCollision(level);

      LOGGER.info("Map loaded: " + level);
      return level;
    } catch (SlickException e) {
      throw new IllegalStateException(e);
    }
  }

  private void constructCollision(LevelMap level) {
    List<GameVector2f> collisionTiles = findAllTiles(TILE_COLLISION, LAYER_GAMEPLAY_LOGIC, level);
    if (collisionTiles.isEmpty())
      throw new IllegalStateException("Map has no collision data. Map: " + level);

    List<RectBound> collisionBounds = new LinkedList<RectBound>();
    for (GameVector2f collisionTile : collisionTiles) {
      Rectangle rect = new Rectangle(
          (int) collisionTile.getX() * TILE_SIZE.width,
          (int) collisionTile.getY() * TILE_SIZE.height,
          TILE_SIZE.width,
          TILE_SIZE.height);
      collisionBounds.add(new RectBound(rect, CollisionBound.CollisionType.SOLID));
    }

    level.setSolidCollision(collisionBounds);
  }

  private void validateMap(LevelMap level) {
    TiledMap map = level.getMap();
    String mapName = level.getFilename();

    if (map.getLayerCount() != LAYERS_COUNT)
      throw new IllegalStateException("Map '" + mapName + "' doesn't have 2 layers: Representation and Gameplay");
/*
    if (map.getLayerIndex(LAYER_REPRESENTATION_BACK) == -1)
      throw new IllegalStateException("Map '" + mapName + "' doesn't have 'Representation' layer");
    if (map.getLayerIndex(LAYER_GAMEPLAY_LOGIC) == -1)
      throw new IllegalStateException("Map '" + mapName + "' doesn't have 'Gameplay' layer");
*/
    if (map.getTileWidth() != TILE_SIZE.width)
      throw new IllegalStateException("Map '" + mapName + "' tile size is not 32x32. Tile width is: " + map.getTileWidth());
    if (map.getTileHeight() != TILE_SIZE.height)
      throw new IllegalStateException("Map '" + mapName + "' tile size is not 32x32. Tile height is: " + map.getTileHeight());
  }

  private void findStartPosition(LevelMap level) {
//    int gameplayLayerIndex = currentMap.getMap().getLayerIndex(LAYER_GAMEPLAY_LOGIC);
    GameVector2f tilePos = findTile(TILE_START_POSITION, LAYER_GAMEPLAY_LOGIC, level);
    if (tilePos == null)
      throw new IllegalStateException("Map doesn't have a start position. Map: " + level);

    tilePos.y++;
    level.setStartPosition((GameVector2f) tilePos.scale(TILE_SIZE.width));
  }

  private List<GameVector2f> findAllTiles(int tileId, int layer, LevelMap level) {
    TiledMap map = level.getMap();
    TileSet tileset = level.getMap().getTileSet(TILESET_GAMEPLAY);
    List<GameVector2f> returnable = new LinkedList<GameVector2f>();

    for (int y = 0; y < map.getHeight(); y++) {
      for (int x = 0; x < map.getWidth(); x++) {
        int localId = map.getTileId(x, y, layer) - tileset.firstGID;
        if (localId == tileId)
          returnable.add(new GameVector2f(x, y));
      }
    }

    return returnable;
  }

  private GameVector2f findTile(int tileId, int layer, LevelMap level) {
    TiledMap map = level.getMap();
    TileSet tileset = level.getMap().getTileSet(TILESET_GAMEPLAY);

    for (int y = 0; y < map.getHeight(); y++) {
      for (int x = 0; x < map.getWidth(); x++) {
        int localId = map.getTileId(x, y, layer) - tileset.firstGID;
        if (localId == tileId)
          return new GameVector2f(x, y);
      }
    }
    return null;
  }

  public void activateMap(LevelMap map) {
    currentMap = map;
    LOGGER.info("Activating map: " + map);
  }

  public LevelMap getCurrentMap() {
    return currentMap;
  }

  public GameVector2f getPlayerStart() {
    if (currentMap == null)
      throw new IllegalStateException("No map active!");
    return currentMap.getStartPosition();
  }
}
